/***************************************************************************
 * Copyright 2012-2013 TXT e-solutions SpA
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * This work was performed within the IoT_at_Work Project
 * and partially funded by the European Commission's
 * 7th Framework Programme under the contract ICT-257367.
 *
 * Authors:
 *      Salvatore Piccione (TXT e-solutions SpA)
 *
 * Contributors:
 *        Domenico Rotondi (TXT e-solutions SpA)
 **************************************************************************/
package it.txt.ens.client.publisher.example.osgi;

import it.txt.ens.client.publisher.factory.ENSEventFactory;
import it.txt.ens.client.publisher.osgi.RegisteredServices;
import it.txt.ens.core.ENSResource;
import it.txt.ens.core.factory.ENSResourceFactory;
import it.txt.ens.core.factory.URIParseException;
import it.txt.ens.core.util.converter.MapConverter;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.cm.ConfigurationException;

/**
 * @author Salvatore Piccione (TXT e-solutions SpA - salvatore.piccione AT txtgroup.com)
 * @author Domenico Rotondi (TXT e-solutions SpA - domenico.rotondi AT txtgroup.com)
 *
 */
public class Activator implements BundleActivator {
    
    private static final ResourceBundle LOGGING_MESSAGES = ResourceBundle.getBundle(
            "logging-messages/" + Activator.class.getSimpleName(), Locale.ROOT);
    private static final Logger LOGGER = Logger.getLogger(Activator.class.getName());
    private static final String PUBLISHER_IMPLEMENTATION_ID = "default";

    public static final String PROPERTY_NAME_FORMAT = "ENSPublisher-PrinterApp-%1$s.properties";
    public static final String DEFAULT_CONFIG_DIR = "conf/";
    public static final String CONFIG_FILE = "PublisherExample.properties";
    public static final String PUBLISHER_POOL_SIZE_KEY = "publisherPoolSize";
    /**
     * The delay in milliseconds between two event publishings
     */
    public static final String PUBLISHER_DELAY = "ens.publisher.delay";
    public static final String MAX_RANGE = "ens.publisher.max";
    public static final String MIN_RANGE = "ens.publisher.min";
    
    private List<ConfiguredPublisherTracker> publisherTrackers;
    private int publisherPoolSize;
    private ExecutorService executor;
    private long shutdownTimeout;
    
    /* (non-Javadoc)
     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
     */
    @Override
    public void start(BundleContext context) throws Exception {
        if (LOGGER.isLoggable(Level.INFO)) {
            LOGGER.log(Level.INFO, "Started bundle '" + context.getBundle().getSymbolicName() + 
                "'. ID: " + context.getBundle().getBundleId());
        }
        
        //load the configuration for this bundle
        Properties config = new Properties();
        FileInputStream stream = null;
        File configFile = new File (DEFAULT_CONFIG_DIR + CONFIG_FILE);
        try {
            stream = new FileInputStream(configFile);
            config.load(stream);
        } catch (Exception e) {
            LOGGER.log(Level.WARNING, MessageFormat.format(
                LOGGING_MESSAGES.getString("errorWhileReadingConfigFile"), 
                configFile.getAbsolutePath()), e);
        } finally {
            if (stream != null)
                stream.close();
        }
        publisherPoolSize = Integer.parseInt(config.getProperty(PUBLISHER_POOL_SIZE_KEY));
        executor = Executors.newFixedThreadPool(publisherPoolSize);
        publisherTrackers = new ArrayList<ConfiguredPublisherTracker>(publisherPoolSize);
        
        //retrieve the configuration admin service
        ServiceReference<ConfigurationAdmin> configAdminSR = context.getServiceReference(ConfigurationAdmin.class);
        if (configAdminSR == null)
            throw new Exception("No " + ConfigurationAdmin.class.getName() + " available");
        ConfigurationAdmin configAdmin = context.getService(configAdminSR);
        File currentPropertiesFile;
        Properties currentProperties;
        Configuration currentConfig;
//        FileInputStream stream = null;
        Dictionary<String,Object> configuration;
        int i = 1;
        ConfiguredPublisherTracker publisherTracker;
        long sleepTime;
        while ((currentPropertiesFile = new File(DEFAULT_CONFIG_DIR + String.format(PROPERTY_NAME_FORMAT, i))).exists() &&
                i <= publisherPoolSize) {
            currentProperties = new Properties();
            try {
                stream = new FileInputStream(currentPropertiesFile);
                currentProperties.load(stream);

                currentConfig = configAdmin.createFactoryConfiguration(RegisteredServices.PUBLISHER_MANAGED_SERVICE_FACTORY_PID, null);
            } catch (IOException e) {
                LOGGER.log(Level.WARNING, MessageFormat.format(
                        LOGGING_MESSAGES.getString("errorWhileReadingPropertiesFile"), 
                        currentPropertiesFile.getAbsolutePath()), e);
                continue;
            } finally {
                if (stream != null)
                    try {
                        stream.close();
                    } catch (Exception e) {}
            }

            configuration = MapConverter.convert(currentProperties);
            configuration.put(it.txt.ens.client.publisher.osgi.FilterAttributes.PUBLISHER_OWNER_KEY,
                    (Long)context.getBundle().getBundleId());
            configuration.put(it.txt.ens.client.publisher.osgi.FilterAttributes.PUBLISHER_IMPLEMENTATION_KEY,
                    PUBLISHER_IMPLEMENTATION_ID);
            currentConfig.update(configuration);
            
            Object patternOBJ = configuration.get(ENSResourceFactory.PATTERN);
            String pattern;
            String namespace;
            if (patternOBJ == null) {
                Object resourceOBJ = configuration.get(ENSResourceFactory.URI);
                if (resourceOBJ == null)
                    throw new ConfigurationException(ENSResourceFactory.URI, "Missing property");
                else {
                    ServiceReference<ENSResourceFactory> resourceFactorySR = context.getServiceReference(
                        ENSResourceFactory.class);
                    ENSResourceFactory resourceFactory = context.getService(resourceFactorySR);
                    ENSResource resource;
                    try {
                        resource = resourceFactory.create(new URI(resourceOBJ.toString()));
                        pattern = resource.getPattern();
                        namespace = resource.getNamespace();
                    } catch (IllegalArgumentException e) {
                        throw new ConfigurationException(ENSResourceFactory.URI, e.getMessage(), e);
                    } catch (URIParseException e) {
                        throw new ConfigurationException(ENSResourceFactory.URI, e.getMessage(), e);
                    } catch (URISyntaxException e) {
                        throw new ConfigurationException(ENSResourceFactory.URI, e.getMessage(), e);
                    }
                    context.ungetService(resourceFactorySR);
                }
            } else {
                pattern = (String) patternOBJ;
                namespace = (String) configuration.get(ENSResourceFactory.NAMESPACE);
            }
            if (LOGGER.isLoggable(Level.FINE))
                LOGGER.log(Level.FINE, "Pattern:: " + pattern);
            
//            EqualsFilter implementationFilter = new EqualsFilter(
//                    it.txt.ens.client.publisher.osgi.FilterAttributes.PUBLISHER_IMPLEMENTATION_KEY, 
//                    PUBLISHER_IMPLEMENTATION_ID);
//            EqualsFilter ownershipFilter = new EqualsFilter(
//                    it.txt.ens.client.publisher.osgi.FilterAttributes.PUBLISHER_OWNER_KEY,
//                    (int) context.getBundle().getBundleId());
//            AndFilter filter = new AndFilter().and(implementationFilter).and(ownershipFilter);
            PublisherFilter filter = new PublisherFilter(PUBLISHER_IMPLEMENTATION_ID, context.getBundle().getBundleId(), namespace, pattern);
            
            try {
                sleepTime = Long.parseLong(currentProperties.getProperty(PUBLISHER_DELAY));
                publisherTracker = new ConfiguredPublisherTracker(context, filter,
                        context.getServiceReference(ENSEventFactory.class),
                        sleepTime,
                        Integer.parseInt(currentProperties.getProperty(MAX_RANGE)),
                        Integer.parseInt(currentProperties.getProperty(MIN_RANGE)));
                publisherTrackers.add(publisherTracker);
                executor.execute(publisherTracker);
                shutdownTimeout += sleepTime;
            } catch (Exception e) {
                LOGGER.log(Level.WARNING,MessageFormat.format(
                        LOGGING_MESSAGES.getString("trackerConfigError"),currentPropertiesFile.getAbsolutePath()),e);
            }
            i++;
        }
    }

    /* (non-Javadoc)
     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
     */
    @Override
    public void stop(BundleContext context) throws Exception {
        for (ConfiguredPublisherTracker tracker : publisherTrackers)
            tracker.shutdown(context);
        executor.shutdown();
        if (LOGGER.isLoggable(Level.FINE))
            LOGGER.log(Level.FINE, MessageFormat.format(LOGGING_MESSAGES.getString("bundleShuttingDown"), shutdownTimeout));
            
        if (!executor.awaitTermination(shutdownTimeout, TimeUnit.MILLISECONDS))
            executor.shutdownNow();

        if (LOGGER.isLoggable(Level.INFO)) {
            LOGGER.log(Level.INFO, "Stopped bundle '" + context.getBundle().getSymbolicName() + 
                "'. ID: " + context.getBundle().getBundleId());
        }
    }
 
}
